import fs from "fs";
import path from "path";
import vm from "vm";
import { ComponentInfo } from "./ComponentInfo";
import { ComponentClass } from "../../../component";

export class LoadComponent {
  private componentsPath: string; // = path.resolve("../first/components/");
  private componentName: string = "";
  // componentIndex: string = "";
  private ci: ComponentInfo;

  constructor(ci: ComponentInfo) {
    // this.componentsPath = path.join(projectRoot, "../first/components");
    this.componentsPath = ci.getComponentPath();
    // console.log(this.componentsPath, "componentsPath");
    this.componentName = ci.getComponentName();
    this.ci = ci;
  }

  private loadCodeFile() {
    const file = fs.readFileSync(this.ci.getComponentIndexJs()).toString();

    return file;
  }

  public loadComponent() {
    const componentFolder = fs
      .readdirSync(this.componentsPath)
      .find((val) => val === this.componentName);

    if (componentFolder === undefined) throw Error("component does not exist!");

    interface sample {
      name: string;
      params: string[];
      entity: (...args: any) => any;
    }
    // mock require
    const repository: sample[] = [];

    const collectParams = (arr: string[], exports: object) => {
      return arr.map((val) => {
        if (val === "exports") return exports;
        val = path.join(this.componentsPath, val);
        if (cache[val]) return cache[val];
        let rep = repository.find((rep) => rep.name === val);
        if (rep === undefined) {
          if (!fs.existsSync(val)) return undefined;
          aux.window.modulePath = val;
          const defineModule = fs.readFileSync(val).toString();
          const script = new vm.Script(defineModule);
          vm.createContext(aux);
          script.runInContext(aux);

          rep = repository.find((rep) => rep.name === val) as sample;
        }

        let exportsObj: any = {};
        // console.log(rep, "rep");

        const ret = rep.entity(...collectParams(rep.params, exportsObj));
        cache[val] = ret ? ret : exportsObj;

        return cache[val];
      });
    };

    const moduleRequire = (...args: any) => {
      let prep: any[] = [];
      if (Object.prototype.toString.call(args[0]) === "[object Array]") {
        const first = args[0] as string[];
        const exportObj = {};
        prep = collectParams(first, exportObj);
      }

      if (Object.prototype.toString.call(args[1]) === "[object Function]") {
        args[1](...prep);
      }
    };

    const cache: { [moduleName: string]: any } = {
      require: moduleRequire,
    };

    // mock require end
    const script = new vm.Script(
      this.loadCodeFile() +
      `\n require(['/${this.componentName}/res/js/${this.componentName}_index.js'], (module)=>{ window.auxClass = Object.values(module)[0] })`
    );

    type defineFun = (...args: any) => undefined | object;
    const aux = {
      window: {
        auxClass: null,
        modulePath: this.ci.getComponentIndexJs(), //this.componentIndex,
      },
      require: moduleRequire,
      define: (params: string[], fun: defineFun) => {
        const type = typeof params;
        if (type === "object")
          repository.push({
            name: aux.window.modulePath,
            params,
            entity: fun,
          });
        else if (type === "function")
          repository.push({
            name: aux.window.modulePath,
            params: [],
            entity: params as unknown as defineFun,
          });
        else console.log("error!");
      },
      // console,
    };

    vm.createContext(aux);

    script.runInContext(aux);

    return aux.window.auxClass as unknown as ComponentClass<
      cheerio.Root,
      cheerio.Cheerio
    // DescCompoundType<cheerio.Root, cheerio.Cheerio>
    >;
  }
}
